﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CLUBsInterpreter.AST;

namespace CLUBsInterpreter.ObjectTypes
{
	/**
	 * <summary>
	 * Describes an abstract object in the CLUBs environment.
	 * </summary>
	 **/
	abstract class CLUBsObject
	{
		/**
		 * <summary>
		 * The native C# code to be executed when evaluating this object.
		 * In the case of an action, it is executed when calling it.
		 * In all other cases, it is executed when the object is declared to set user-specified values.
		 * </summary>
		 **/
		public Action TranslatedMethod = null;
		public Action ReturnMethod = null;

		public string Name = "Anonymous";

		/**
		 * <summary>
		 * The identifier type of this object.
		 * </summary>
		 **/
		public IdentifierType ObjectIdentifierType = IdentifierType.Unset;
		/**
		 * <summary>
		 * The local variables in this object's local scope, accessed by name.
		 * </summary>
		 **/
		public Scope LocalVariables = new Scope();

		/**
		 * <summary>
		 * Gets the type of a CLUBsObject with the specified ObjectIdentifierType.
		 * DEPRECATED: This may be useless. Unless we find a use for it, it will be removed.
		 * </summary>
		 **/
		public static Type GetObjectType(IdentifierType type)
		{
			switch (type)
			{
				case IdentifierType.Action:
					return typeof(CLUBsAction);
				case IdentifierType.Board:
					return typeof(CLUBsBoard);
				case IdentifierType.Cell:
					return typeof(CLUBsCell);
				case IdentifierType.Piece:
					return typeof(CLUBsPiece);
				case IdentifierType.Player:
					return typeof(CLUBsPlayer);
				case IdentifierType.Round:
					return typeof(CLUBsRound);
				case IdentifierType.Turn:
					return typeof(CLUBsTurn);
				case IdentifierType.Integer:
					return typeof(CLUBsInteger);
				case IdentifierType.String:
					return typeof(CLUBsString);
				case IdentifierType.Boolean:
					return typeof(CLUBsBoolean);
				default:
					return null;
			}
		}

		/**
		 * <summary>
		 * Generates an object for the specified definition, of the specified type.
		 * </summary>
		 **/
		public static CLUBsObject GenerateObject(IdentifierType type, DefinitionNode node)
		{
			CLUBsObject returnVal;
			switch (type)
			{
				case IdentifierType.Action:
					{
						ActionDefNode an = (ActionDefNode)node;
						an.Action = new CLUBsAction();
						node.ObjectReference = an.Action;
						returnVal = an.Action;
						break;
					}
				case IdentifierType.Board:
					{
						BoardDefNode bn = (BoardDefNode)node;
						bn.Board = new CLUBsBoard();
						node.ObjectReference = bn.Board;
						returnVal = bn.Board;
						break;
					}
				case IdentifierType.Cell:
					{
						CellDefNode cn = (CellDefNode)node;
						cn.Cell = new CLUBsCell();
						node.ObjectReference = cn.Cell;
						returnVal = cn.Cell;
						break;
					}
				case IdentifierType.Piece:
					{
						PieceDefNode pn = (PieceDefNode)node;
						pn.Piece = new CLUBsPiece(true); // NOTE THIS DELICIOUSLY UGLY HACK
						node.ObjectReference = pn.Piece;
						returnVal = pn.Piece;
						break;
					}
				case IdentifierType.Player:
					{
						PlayerDefNode pn = (PlayerDefNode)node;
						pn.Player = new CLUBsPlayer(true); // NOTE THIS DELICIOUSLY UGLY HACK TOO
						node.ObjectReference = pn.Player;
						returnVal = pn.Player;
						break;
					}
				case IdentifierType.Round:
					{
						RoundDefNode rn = (RoundDefNode)node;
						rn.Round = new CLUBsRound();
						node.ObjectReference = rn.Round;
						returnVal = rn.Round;
						break;
					}
				case IdentifierType.Turn:
					{
						TurnDefNode tn = (TurnDefNode)node;
						tn.Turn = new CLUBsTurn();
						node.ObjectReference = tn.Turn;
						returnVal = tn.Turn;
						break;
					}
				default:
					returnVal = null;
					break;
			}

			returnVal.ObjectIdentifierType = type;

			return returnVal;
		}

		/**
		 * <summary>
		 * Generates a simple object (int, bool, string) of the specified type.
		 * </summary>
		 **/
		public static CLUBsObject GenerateSimpleObject(IdentifierType type)
		{
			switch (type)
			{
				case IdentifierType.Integer:
					{
						return new CLUBsInteger();
					}
				case IdentifierType.String:
					{
						return new CLUBsString();
					}
				case IdentifierType.Boolean:
					{
						return new CLUBsBoolean();
					}
				case IdentifierType.Action:
					{
						return new CLUBsAction();
					}
				case IdentifierType.Board:
					{
						return new CLUBsBoard();
					}
				case IdentifierType.Cell:
					{
						return new CLUBsCell();
					}
				case IdentifierType.Piece:
					{
						return new CLUBsPiece();
					}
				case IdentifierType.Player:
					{
						return new CLUBsPlayer();
					}
				case IdentifierType.Round:
					{
						return new CLUBsRound();
					}
				case IdentifierType.Turn:
					{
						return new CLUBsTurn();
					}
				case IdentifierType.Null:
					{
						return new CLUBsNull();
					}
				default:
					return null;
			}
		}

		/**
		 * <summary>
		 * Executes the translated method.
		 * This should mostly not be called directly.
		 * Actions should be called through the StandardEnvironment.CallAction method.
		 * </summary>
		 **/
		public void Execute()
		{
			CLUBsObject.Execute(TranslatedMethod, ReturnMethod, this);
		}
		public static void Execute(Action method, Action retMethod = null, CLUBsObject executable = null)
		{
			if (method != null)
			{
				Delegate[] invocations = method.GetInvocationList();
				Action currentInvocation;
				int i = 0;
				while (!(CLUBsBoolean)StandardEnvironment.Instance.GlobalVariables["GameHasEnded"] &&
					!StandardEnvironment.Instance.ExecutionReturnTriggered)
				{
					currentInvocation = (Action)invocations[i];
					//invocations[i].DynamicInvoke();
					currentInvocation();
					
					if (++i == invocations.Length)
					{
						break;
					}
				}

				/*if (executable != null && executable.LocalVariables.Parent == null &&
					method != StandardEnvironment.Instance.Action_give)
				{
					//if (StandardEnvironment.Instance.ExecutionReturnTriggered)
						Console.WriteLine("Finished returning from "+executable);
					StandardEnvironment.Instance.ExecutionReturnTriggered = false;
				}*/

				if (i > 0 && retMethod != null)
				{
					retMethod();
				}
			}
		}

		/**
		 * <summary>
		 * CopyValueFromObject must overwrite the internal value with the one in the object given.
		 * </summary>
		 **/
		public abstract void CopyValueFromObject(CLUBsObject obj);

		public static CLUBsBoolean operator ==(CLUBsObject obj1, CLUBsObject obj2)
		{
			if ((Object)obj1 == null || obj1.ObjectIdentifierType == IdentifierType.Null)
			{
				if ((Object)obj2 == null || obj2.ObjectIdentifierType == IdentifierType.Null)
					return true;
				else
					return false;
			}
			else if ((Object)obj2 == null || obj2.ObjectIdentifierType == IdentifierType.Null)
			{
				if ((Object)obj1 == null || obj1.ObjectIdentifierType == IdentifierType.Null)
					return true;
				else
					return false;
			}

			switch (obj1.ObjectIdentifierType)
			{
				case IdentifierType.Integer:
					return ((CLUBsInteger)obj1).InternalInteger == ((CLUBsInteger)obj2).InternalInteger;
				case IdentifierType.Board:
					return ((CLUBsBoard)obj1).Equals((CLUBsBoard)obj2);
				case IdentifierType.Boolean:
					return ((CLUBsBoolean)obj1).InternalBoolean == ((CLUBsBoolean)obj2).InternalBoolean;
				case IdentifierType.Cell:
					return ((CLUBsCell)obj1).Equals((CLUBsCell)obj2);
				case IdentifierType.Piece:
					return ((CLUBsPiece)obj1).PrototypeID == ((CLUBsPiece)obj2).PrototypeID;
				case IdentifierType.Player:
					return ((CLUBsPlayer)obj1).PrototypeID == ((CLUBsPlayer)obj2).PrototypeID;
				case IdentifierType.Round:
					return ((CLUBsRound)obj1).Equals((CLUBsRound)obj1);
				case IdentifierType.String:
					return ((CLUBsString)obj1).InternalString == ((CLUBsString)obj2).InternalString;
				case IdentifierType.Turn:
					return ((CLUBsTurn)obj1).Equals((CLUBsTurn)obj1);
				default:
					return null;
			}
		}
		public static CLUBsBoolean operator !=(CLUBsObject obj1, CLUBsObject obj2)
		{
			return !(obj1 == obj2);
		}

		public override bool Equals(object obj)
		{
			return base.Equals(obj);
		}

		public override int GetHashCode()
		{
			return base.GetHashCode();
		}
	}
}
